/*                          UX-Framework                               
 *                                                                 
 *  iZE Co.,Ltd. Copyright 2009 All rights reserved.          
 *  This file is distributed under the terms of the GNU Lesser 
 *  General Public License, described in file LICENSE.                                     
 */
package jp.ize.ux.acts;

// {{{ imports
import jp.ize.ux.UXObject;
import jp.ize.ux.acts.Act;
import jp.ize.ux.geom.UXPoint;
import jp.ize.ux.utils.Util;
import jp.ize.ux.tween.UXTween;
// }}}
// {{{ typedef LinearMovable
/**
 * LinearActが必要な情報です。
 */
typedef LinearMovable = {
	> ListStateType,
	/** 始点 */
	var startp(default, default) : UXPoint;
	/** 終点 */
	var endp(default, default) : UXPoint;
	/** 幅 */
	var width(default, default) : Float;
}
// }}}
// {{{ class LinearAct extends Act
/**
 * LinearActクラス<br>
 * 直線上の動きを表現するクラスです。<br>
 *
 * @author		iZE Co., Ltd.<br>
 * @version		$Id$<br>
 */
class LinearAct extends Act
{
	// {{{ public function new()
	/**
	 * コンストラクタ。
	 */
	public function new()
	{
		super();
	}
	// }}}
	// {{{ public function layout( ?ap:ActParameter, ?interp:LinearMovable->ActParameter ) : LinearMovable -> Void
	/**
	 * 直線上に子コンポーネントを並べるアクションです。
	 */
	public function layout( ?ap:ActParameter, ?interp:LinearMovable->ActParameter ) : LinearMovable -> Void
	{
		var me = this;
		return function( state : LinearMovable ) : Void {
			var param = ap;
			if ( interp!=null ) param = interp(state);
			me.doLayout(state,param);
			me.retouchZ(state,state.children.length-1);
		};
	}
	// }}}
	// {{{ public function move( ?mp:MoveParameter, ?interp:LinearMovable->MoveParameter ) : LinearMovable -> Void
	/**
	 * 子コンポーネントを移動量分だけ移動させるアクションです。
	 * stateのcyclicがtrueの場合でないと動作しません。
	 */
	public function move( ?mp:MoveParameter, ?interp:LinearMovable->MoveParameter ) : LinearMovable -> Void
	{
		var me = this;
		return function( state : LinearMovable ) : Void {
			if ( state.cyclic ) {
				var param = mp;
				if ( interp!=null ) param = interp(state);
				var cs:Array<UXObject> = state.children.copy();
				for ( i in 0...state.children.length ) {
					var dir:Int = 0;
					var idx:Int = 0;
					if ( i+param.delta<0 ) {
						idx = i+param.delta+state.children.length;
						dir = -1 * Std.int(Math.abs(param.delta));
					} else if ( i+param.delta>state.children.length-1 ) {
						idx = (i+param.delta)-state.children.length;
						dir = Std.int(Math.abs(param.delta));
					} else {
						idx = i+param.delta;
					}
					state.children[i] = cs[idx];
					if ( dir>0 ) {
						state.children[i].x = state.endp.x + 20;
						state.children[i].y = state.endp.y + 20;
					} else if ( dir<0 ) {
						state.children[i].x = state.startp.x - 20;
						state.children[i].y = state.startp.y - 20;
					}
				}
				me.doLayout(state,param);
				me.retouchZ(state,state.children.length-1);
			}
		};
	}
	// }}}
	// {{{ public function wipe( wp:WipeParameter, ?interp:LinearMovable->WipeParameter ) : LinearMovable -> Void
	/**
	 * 直線上を子コンポーネントが重なるように移動するアクションです。
	 */
	public function wipe( wp:WipeParameter, ?interp:LinearMovable->WipeParameter ) : LinearMovable -> Void
	{
		var me = this;
		return function( state : LinearMovable ) : Void {
			me.initSync(state.children.length);
			me.setSyncDoneHandler( function(){
				if ( state.subject!=null ) {
					state.subject.notify(Act.STOP);
				}
			});
			var param = wp;
			if ( interp!=null ) param = interp(state);
			me.retouchZ(state,param.center);
			var cx:Float = state.children[param.center].x;
			var cy:Float = state.children[param.center].y;
			for (i in 0...state.children.length) {
				if ( i==param.center ) continue;
				var c = state.children[i];
				var getter = function(obj:UXObject): {x:Float,y:Float} {
					return {x:obj.x,y:obj.y};
				};
				var setter = function(obj:UXObject, init:{x:Float,y:Float}, end:{x:Float,y:Float}, v:Float) {
					obj.x = init.x+(end.x-init.x)*v;
					obj.y = init.y+(end.y-init.y)*v;
				};
				var end = {x:cx,y:cy};
				var tw = new UXTween<UXObject,{x:Float,y:Float}>(c, end, getter, setter, param.time,
							param.transition);
				tw.setEndTweenHandler( function(e:Dynamic){
					me.doneSync(i);
				});
				var id = "linearwipe" + Std.string(i);
				if ( !me.tm.objects.exists(id) ) {
					me.tm.registObject(id,state);
				}
				me.tm.addTweenChain(id,tw); 
				me.tm.start(id,false,state.target);
			}
		};
	}
	// }}}
	// {{{ public function shuffle( sp:ShuffleParameter, ?interp:LinearMovable->ShuffleParameter ) : LinearMovable -> Void
	/**
	 * 線分上に子コンポーネントをランダムに並べるアクションです。
	 */
	public function shuffle( sp:ShuffleParameter, ?interp:LinearMovable->ShuffleParameter ) : LinearMovable -> Void
	{
		var me = this;
		return function( state : LinearMovable ) : Void {
			var param = sp;
			if ( interp!=null ) param = interp(state);
			if ( param.targets==null ) {
				param.targets = new Array();
				for ( i in 0...state.children.length ) {
					param.targets.push(i);
				}
			}
			var dup = param.targets.copy();
			var table:Array<Int> = new Array();
			var func = function() : Int {
				var idx:Int = Std.int(Math.random()*dup.length);
				var b:Int = dup[idx];
				dup.remove(b);
				return b;
			};
			while ( dup.length>0 ) {
				table.push( func() );
			}
			var cs:Array<UXObject> = state.children.copy();
			for ( i in 0...param.targets.length ) {
				// 0123
				// 23
				var idx1 = param.targets[i];
				// 32
				var idx2 = table[i];   
				state.children[idx1] = cs[idx2];
				// 0132
			}
			me.doLayout(state,param);
			me.retouchZ(state,state.children.length-1);
		};
	}
	// }}}
	// {{{ public function fishEye( fp:FishEyeParameter, ?interp:LinearMovable->FishEyeParameter ) : LinearMovable -> Void
	/**
	 * マウスの動きに合わせて魚眼ズームするアクションです。周りの子コンポーネントはつられて押し出されます。
	 */
	public function fishEye( fp:FishEyeParameter, ?interp:LinearMovable->FishEyeParameter ) : LinearMovable -> Void
	{
		var me = this;
		return function( state : LinearMovable ) : Void {
			var param = fp;
			if ( interp!=null ) param = interp(state);
			var centerScale:Float = param.centerScale;
			var effectRange:Int   = param.effectRange;
			var dx:Float = state.endp.x - state.startp.x;
			var dy:Float = state.endp.y - state.startp.y;
			var scaleCenterIdx:Int = -1;
			var scales:Array<Float> = new Array();
			var dists:Array<Float> = new Array();
			for (i in 0...state.children.length) {
				if ( state.children[i]==state.eventTarget ) {
					scaleCenterIdx = i;
				}
				scales[i] = 1.0;
				dists[i] = i;
			}
			var s_range:Int = scaleCenterIdx - effectRange;
			var e_range:Int = scaleCenterIdx + effectRange;
			var d_scale:Float = (centerScale-1.0)/2/effectRange;
			for ( i in -effectRange...effectRange+1 ) {
				var idx:Int = scaleCenterIdx+i;
				if ( idx>=0 && idx<state.children.length ) {
					if ( i<0 ) {
						scales[idx] = 1.0 + (i+effectRange+1)*d_scale;
						dists[idx]  += (scales[idx]-1.0)/(effectRange*1.5);
					} else if ( i==0 ) {
						scales[idx] = centerScale;
					} else if ( i>0 ) {
						scales[idx] = 1.0 + (effectRange-i+1)*d_scale;
						dists[idx]  += (scales[idx]-1.0)/(effectRange*1.5);
					}
				}
			}
			for (i in 0...state.children.length) {
				var c = state.children[i];
				c.scaleX = scales[i];
				c.scaleY = scales[i];
				var goal_x:Float = state.startp.x + dx*dists[i]/state.children.length;
				var goal_y:Float = state.startp.y + dy*dists[i]/state.children.length;
				c.x = goal_x;
				c.y = goal_y;
			}
			me.retouchZ(state,scaleCenterIdx);
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		};
	}
	// }}}
	// {{{ public function retouchZ( state:LinearMovable, center:Int ) : Void
	/**
	 * 子要素の前後関係を調整します
	 */
	public function retouchZ( state:LinearMovable, center:Int ) : Void
	{
		var ary:Array<Int> = new Array();
		var cnt:Int = 1;
		ary.push(center);
		while ( cnt<state.children.length ) {
			if ( center-cnt>=0 ) {
				ary.push(center-cnt);
			}
			if ( center+cnt<state.children.length ) {
				ary.push(center+cnt);
			}
			cnt++;
		}
		ary.reverse();
		for ( i in 0...state.children.length ) {
			var c = state.children[ary[i]];
			state.children[0].parent.setChildIndex( c, i );
		}
	}

	// }}}
	// {{{ public function doLayout( state:LinearMovable, param:ActParameter ) : Void
	/**
	 * 実際のレイアウト・アニメーションを実装します
	 */
	public function doLayout( state:LinearMovable, param:ActParameter ) : Void
	{
		var me = this;
		initSync(state.children.length);
		setSyncDoneHandler( function(){
			if ( state.subject!=null ) {
				state.subject.notify(Act.STOP);
			}
		});
		var dx:Float = state.endp.x - state.startp.x;
		var dy:Float = state.endp.y - state.startp.y;
		for (i in 0...state.children.length) {
			var c = state.children[i];
			var goal_x:Float = state.startp.x + i*dx/state.children.length;
			var goal_y:Float = state.startp.y + i*dy/state.children.length;
			var getter = function(obj:UXObject): {x:Float,y:Float} {
				return {x:obj.x,y:obj.y};
			};
			var setter = function(obj:UXObject, init:{x:Float,y:Float}, end:{x:Float,y:Float}, v:Float) {
				obj.x = init.x+(end.x-init.x)*v;
				obj.y = init.y+(end.y-init.y)*v;
			};
			var end = {x:goal_x,y:goal_y};
			var tw = new UXTween<UXObject,{x:Float,y:Float}>(c, end, getter, setter, param.time,
						param.transition);
			tw.setEndTweenHandler( function(e:Dynamic){
				me.doneSync(i);
			});
			var id = "linearlayout" + Std.string(i);
			if ( !me.tm.objects.exists(id) ) {
				me.tm.registObject(id,state);
			}
			me.tm.addTweenChain(id,tw); 
			me.tm.start(id,false,state.target);
		}
	}

	// }}}
}
// }}}
